/*******************************************************************************
 * Copyright 2011 Mauro Luigi Drago (drago.luigi@gmail.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *******************************************************************************/
package com.bytenose.extrautils.collect;

import java.util.Iterator;

import com.bytenose.extrautils.annotations.NonNullable;
import com.bytenose.extrautils.annotations.UtilityClass;
import com.bytenose.extrautils.functional.Method2;
import com.bytenose.extrautils.functional.Method3;
import com.bytenose.extrautils.functional.Method4;

/**
 * Defines weak wise iteration methods.
 * A weak wise iterator is an iterator not requiring
 * that all the joined iterators have the same size.
 * @author Mauro Luigi Drago
 *
 */
@UtilityClass
final class WeakWiseIterators {
	/**
	 * Private constructor to avoid instantiation.
	 */
	private WeakWiseIterators() {
	}
	
	/**
	 * Iterates stepwise over a set of iterators and applies a specified method 
	 * to each element tuple.
	 * The iterators may yield a different number of values. If this is the case, i.e., some
	 * elements miss, the method is invoked with a null argument for the corresponding
	 * parameter.
	 * @param <T1> the type of the first argument.
	 * @param <T2> the type of the second argument.
	 * @param it1 the first iterable, cannot be null.
	 * @param it2 the second iterable, cannot be null.
	 * @param method the method to apply, cannot be null.
	 * 
	 * @throws IllegalArgumentException 
	 * 		if some of the {@link NonNullable} arguments are null.
	 */
	static <T1, T2> void applyWiseWeak(
			@NonNullable Iterator<T1> it1, @NonNullable Iterator<T2> it2, 
			@NonNullable Method2<T1,T2> method) 
			throws IllegalArgumentException {
		if (it1 == null) throw new IllegalArgumentException("it1");
		if (it2 == null) throw new IllegalArgumentException("it2");
		if (method == null) throw new IllegalArgumentException("method");
		
		// We cache the values inside variables, to avoid calling
		// two times hasNext for the same iterator for every cycle.
		boolean hasNext1, hasNext2;
		hasNext1 = it1.hasNext();
		hasNext2 = it2.hasNext();
		
		while (hasNext1 || hasNext2) {
			T1 arg1 = hasNext1 ? it1.next() : null;
			T2 arg2 = hasNext2 ? it2.next() : null;
			
			method.apply(arg1, arg2);
		}
	}
	
	/**
	 * Iterates stepwise over a set of iterators and applies a specified method 
	 * to each element tuple.
	 * The iterators may yield a different number of values. If this is the case, i.e., some
	 * elements miss, the method is invoked with a null argument for the corresponding
	 * parameter.
	 * @param <T1> the type of the first argument.
	 * @param <T2> the type of the second argument.
	 * @param <T3> the type of the third argument.
	 * @param it1 the first iterable, cannot be null.
	 * @param it2 the second iterable, cannot be null.
	 * @param it3 the third iterable, cannot be null.
	 * @param method the method to apply, cannot be null.
	 * 
	 * @throws IllegalArgumentException 
	 * 		if some of the {@link NonNullable} arguments are null.
	 */
	static <T1, T2, T3> void applyWiseWeak(
			@NonNullable Iterator<T1> it1, @NonNullable Iterator<T2> it2, 
			@NonNullable Iterator<T3> it3,
			@NonNullable Method3<T1,T2,T3> method)
			throws IllegalArgumentException {
		if (it1 == null) throw new IllegalArgumentException("it1");
		if (it2 == null) throw new IllegalArgumentException("it2");
		if (it3 == null) throw new IllegalArgumentException("it3");
		if (method == null) throw new IllegalArgumentException("method");
		
		// We cache the values inside variables, to avoid calling
		// two times hasNext for the same iterator for every cycle.
		boolean hasNext1, hasNext2, hasNext3;
		hasNext1 = it1.hasNext();
		hasNext2 = it2.hasNext();
		hasNext3 = it3.hasNext();
		
		while (it1.hasNext()) {
			T1 arg1 = hasNext1 ? it1.next() : null;
			T2 arg2 = hasNext2 ? it2.next() : null;
			T3 arg3 = hasNext3 ? it3.next() : null;
			
			method.apply(arg1, arg2, arg3);
		}
	}
	
	/**
	 * Iterates stepwise over a set of iterators and applies a specified method 
	 * to each element tuple.
	 * The iterators may yield a different number of values. If this is the case, i.e., some
	 * elements miss, the method is invoked with a null argument for the corresponding
	 * parameter.
	 * @param <T1> the type of the first argument.
	 * @param <T2> the type of the second argument.
	 * @param <T3> the type of the third argument.
	 * @param <T4> the type of the fourth argument.
	 * @param it1 the first iterable, cannot be null.
	 * @param it2 the second iterable, cannot be null.
	 * @param it3 the third iterable, cannot be null.
	 * @param it4 the fourth iterable, cannot be null.
	 * @param method the method to apply, cannot be null.
	 * 
	 * @throws IllegalArgumentException 
	 * 		if some of the {@link NonNullable} arguments are null.
	 */
	static <T1, T2, T3, T4> void applyWiseWeak(
			@NonNullable Iterator<T1> it1, @NonNullable Iterator<T2> it2, 
			@NonNullable Iterator<T3> it3, @NonNullable Iterator<T4> it4,
			@NonNullable Method4<T1,T2,T3,T4> method)
			throws IllegalArgumentException {
		if (it1 == null) throw new IllegalArgumentException("it1");
		if (it2 == null) throw new IllegalArgumentException("it2");
		if (it3 == null) throw new IllegalArgumentException("it3");
		if (it4 == null) throw new IllegalArgumentException("it4");
		if (method == null) throw new IllegalArgumentException("method");
		
		// We cache the values inside variables, to avoid calling
		// two times hasNext for the same iterator for every cycle.
		boolean hasNext1, hasNext2, hasNext3, hasNext4;
		hasNext1 = it1.hasNext();
		hasNext2 = it2.hasNext();
		hasNext3 = it3.hasNext();
		hasNext4 = it4.hasNext();
		
		while (it1.hasNext()) {
			T1 arg1 = hasNext1 ? it1.next() : null;
			T2 arg2 = hasNext2 ? it2.next() : null;
			T3 arg3 = hasNext3 ? it3.next() : null;
			T4 arg4 = hasNext4 ? it4.next() : null;
			
			method.apply(arg1, arg2, arg3, arg4);
		}
	}
}
